/*
 * String.c
 *
 * Like Java strings, non-mutable.
 *  Created on: Aug 31, 2008
 *      Author: Michael Gower
 */

#include "../gc6.7/include/gc.h"

#include "String.h"

#include <assert.h>

//#include "../log/Log.h"
//typedef struct StringTag* (*concatFuncSig) (const struct StringTag*, const struct StringTag*);

/*-----------------------------------------------*/
pString String_new(const char* input)
{
	pString ret = GC_malloc(sizeof(String));

	if (input == NULL)
	{
		ret->length = 1;
	} else
	{
		ret->length = strlen(input);
	}

	ret->cString = GC_malloc(ret->length);

	if (input == NULL)
	{
		ret->cString[0] = (char) NULL;
	} else
	{
		strcpy(ret->cString, input);
	}

	ret->concat = String_concat;

	return ret;
}

/*-----------------------------------------------*/
int String_indexOf(const pString input, const char* delims)
{
	int ret = (int) strpbrk(input->cString, delims);
	if (ret == 0)
	{
		return -1;
	}
	ret -= (int) input->cString; // convert from address to index
	if (ret >= input->length || ret < 0)
	{
		return -1;
	}
	return ret;
}


/*-----------------------------------------------*/
pString String_clone(const pString input)
{
	return String_new(input->cString);
}


/*-----------------------------------------------*/
pString String_resize(const pString input, int size)
{
	if (input == NULL)
	{
		return NULL;
	}
	if (size == 0 || size == 1)
	{
		return String_new(NULL);
	}
	if (size == input->length)
	{
		return String_clone(input);
	} else
	{
		pString ret = String_new(NULL);
		ret->length = size;
		ret->cString = GC_malloc(sizeof(char) * ret->length);
		strncpy(ret->cString, input->cString, ret->length);
		return ret;
	}
}


/*-----------------------------------------------*/
pString String_substring(pString input, int start, int end)
{
	if (end == 0 || end > input->length)
	{ // copy until end
		end = input->length;
	}
	if (start > input->length
			|| end < 0 || end > input->length ||
			start > end)
	{
		return NULL;
	} else if (start < 0)
	{ // copy from end
		if (-start > input->length)
		{
			start = -input->length;
		}
		start = input->length + start; // + negativeStart
		end = input->length;
	}

	pString ret = String_new(NULL);
	if (start == end)
	{
		return ret;
	}
	assert(end - start > 0);

	ret = String_resize(ret, end - start);
	assert(ret != NULL);
	strncpy(ret->cString, input->cString+start, end-start);
	return ret;
}


/*-----------------------------------------------*/
pString String_head(const pString input, int n)
{
	if (input == '\0' || n == 0)
	{
		return '\0';
	}
	int windowsEOL = 0;
	pString pStringEOL = String_new("\n");
	if (String_indexOf(input, "\r") != -1)
	{
		windowsEOL = 1;
		pStringEOL = String_new("\r\n");
	}
	pString remainingInput = input;
	pString ret = String_new(NULL);
	int index;
	if (n > 0)
	{
		while (n > 0)
		{

			//printf("loop:%s\n", ret->cString);
			index = String_indexOf(remainingInput, "\r\n");
			//printf("index %d\n", index);
			if (index == -1)
			{ // not found
				index = remainingInput->length;
			} else if (index == 0)
			{ // discard
				if (String_indexOf(remainingInput, "\r") == 0)
				{
					remainingInput = String_substring(remainingInput, 1, 0);
				}
				if (String_indexOf(remainingInput, "\n") == 0)
				{
					remainingInput = String_substring(remainingInput, 1, 0);
				}
				index = String_indexOf(remainingInput, "\r\n");
			}
			// copy line
			ret = String_concat(ret, String_substring(remainingInput, 0, index));
			// concat \n
			if (n > 1)
			{
				ret = String_concat(ret, String_new("\n"));
			}
			// remove line from remainingInput
			remainingInput = String_substring(remainingInput, index, 0);
			n--;
		}
	} else if (n < 0)
	{
		int totalLines = String_lineCount(input);
		int lastLineToCopy = totalLines + n; // n negative number
		int currentLine = 0;
		// loop until we're at the first line to copy
		while(currentLine < lastLineToCopy)
		{
			pString lineToCopy = String_popLineGetLine(remainingInput);
			remainingInput = String_popLineGetStack(remainingInput);
			ret = String_appendLine(ret, lineToCopy);
			currentLine++;
		}
	}

	return ret;
}


/*-----------------------------------------------*/
pString String_trim(const pString input)
{
	const char* pCharInput = input->cString;
	int index = 0;
	while (pCharInput[index] == '\r' ||
			pCharInput[index] == '\n' ||
			pCharInput[index] == ' ' ||
			pCharInput[index] == '\0' ||
			pCharInput[index] == '\t')
	{
		index++;
	}

	int endingIndex = strlen(pCharInput);
	while (pCharInput[endingIndex] == '\r' ||
			pCharInput[endingIndex] == '\n' ||
			pCharInput[endingIndex] == ' ' ||
			pCharInput[endingIndex] == '\0' ||
			pCharInput[endingIndex] == '\t')
	{
		endingIndex--;
	}
	return String_substring(input, index, endingIndex+1);
}


/*-----------------------------------------------*/
pString String_concat(pString first, pString second)
{
	if (first == NULL || first->cString[0] == (char)NULL)
	{
		return String_clone(second);
	} else if (second == NULL || second->cString[0] == (char)NULL)
	{
		return String_clone(first);
	} else
	{
		pString ret = String_clone(first);
		ret = String_resize(ret, first->length + second->length);
		strncpy(ret->cString + first->length, second->cString, second->length);
		return ret;
	}
}


/*-----------------------------------------------*/
int String_equals(const pString first, const pString second)
{
	assert(first != NULL);
	assert(second != NULL);
	return strcmp(first->cString, second->cString) == 0;
}


/*-----------------------------------------------*/
int String_lineCount(const pString input)
{
	if (input == '\0')
	{
		return '\0';
	}
	pString remainingInput = input;
	int ret = 0;
	int index = String_indexOf(remainingInput, "\r\n");

	while(index != -1)
	{ // more lines
		int skip = 0; // move past trailing
		if (index == 0)
		{ // discard
			if (String_indexOf(remainingInput, "\r") == 0)
			{
				remainingInput = String_substring(remainingInput, 1, 0);
			}
			if (String_indexOf(remainingInput, "\n") == 0)
			{
				remainingInput = String_substring(remainingInput, 1, 0);
			}
			index = String_indexOf(remainingInput, "\r\n");
		} else
		{
			skip = 1;
			if (String_indexOf(remainingInput, "\r") ==
					String_indexOf(remainingInput, "\n") - 1)
			{
				skip = 2;
			}
		}
		if (index != -1)
		{
			// remove line from remainingInput
			remainingInput = String_substring(remainingInput, index+skip, 0);
		}
		ret++;
		index = String_indexOf(remainingInput, "\r\n");
	}
	return ret+1;
}


/*-----------------------------------------------*/
pString String_popLineGetLine(const pString input)
{
	if (input == '\0')
	{
		return '\0';
	}
	pString remainingInput = input;
	pString ret = String_new(NULL);
	int index = String_indexOf(remainingInput, "\r\n");
	int windowsEOL = 0;
	if (String_indexOf(remainingInput, "\r") != -1)
	{
		windowsEOL = 1;
	}
	if (index == -1)
	{ // only one line, return all
		return String_concat(String_clone(input), String_new("\n"));
	} else if (index == 0 && windowsEOL)
	{
		return String_new("\r\n");
	} else if (index == 0 && !windowsEOL)
	{
		return String_new("\n");
	}
	ret = String_substring(input, 0, index);
	if (windowsEOL)
	{
		ret = String_concat(ret, String_new("\r\n"));
	} else if (!windowsEOL)
	{
		ret = String_concat(ret, String_new("\n"));
	}
	return ret;

}


/*-----------------------------------------------*/
pString String_popLineGetStack(const pString input)
{
	if (input == '\0')
	{
		return '\0';
	}
	pString remainingInput = input;
	pString ret = String_new(NULL);

	int windowsEOL = 0;
	if (String_indexOf(remainingInput, "\r") != -1)
	{
		windowsEOL = 1;
	}

	int index = String_indexOf(remainingInput, "\r\n");
	if (index == -1)
	{ // only one line, return empty string
		return String_new(NULL);
	}
	remainingInput = String_substring(remainingInput, index, 0);
	if (windowsEOL)
	{
		remainingInput = String_substring(remainingInput, 2, 0); // remove \r\n at start
	} else
	{
		remainingInput = String_substring(remainingInput, 1, 0); // remove \n at start
	}
	return remainingInput;
}


/*-----------------------------------------------*/
pString String_appendLine(const pString base, const pString lineToAppend)
{
	int windowsEOL = 0;
	pString pStringEOL = String_new("\n");
	if (String_indexOf(base, "\r") != -1)
	{
		windowsEOL = 1;
		pStringEOL = String_new("\r\n");
	}

	pString end = String_substring(base, -1-windowsEOL, 0);
	int baseEndsWithNewline = String_equals(pStringEOL, end);
	end = String_substring(lineToAppend, -1-windowsEOL, 0);
	int lineToAppendEndsWithNewline = String_equals(pStringEOL, end);

	pString ret;
	if (base->cString[0] != '\0')
	{
		ret = String_clone(base);
		if (!baseEndsWithNewline)
		{
			ret = String_concat(ret, pStringEOL);
		}
		ret = String_concat(ret, lineToAppend);
	} else
	{
		ret = String_clone(lineToAppend);
	}
	if (!lineToAppendEndsWithNewline)
	{
		ret = String_concat(ret, pStringEOL);
	}
	return ret;
}
